#include "REScan.h"

#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>
#include <stdlib.h>

struct MatchItem
{
	int Type;
	StringSpan Name;

	/// MATCH_LITERAL
	StringSpan Literal;

	/// MATCH_LINK
	MatchItem* Link;

	/// MATCH_GROUP
	MatchItem* FirstChild;
	MatchItem* LastChild;

	MatchItem* NextSibling;
	MatchItem* PrevSibling;

	///              this
	MatchFunction MatchFun;

	///								 this        match text  out name    out value (for scanf)
	ParseFunction OnScanMatch;

	int Negate;

	int Output;
	StringSpan OutName;
	char Multiple;
};


void SSPrint(StringSpan span)
{
	while (span.len --> 0)
		putc(*span.str++, stdout);
}

StringSpan SSMake(const char* start, const char* end)
{
	StringSpan span;
	span.str = start;
	span.len = end-start;
	return span;
}


void SSSet(StringSpan* span, const char* text)
{
	span->len = strlen(text);
	span->str = text;
}

int SSCmp(StringSpan* span1, StringSpan* span2)
{
	return span1->len == span2->len && !strncmp(span1->str, span2->str, span1->len);
}

int SSCmpStr(StringSpan* span1, str text)
{
	return !strncmp(span1->str, text, span1->len);
}

void SSStart(StringSpan* span, str start)
{
	span->str = start;
}

void SSStop(StringSpan* span, str end)
{
	span->len = end - span->str;
}


str SwallowWhitespace(str text)
{
	while (*text && isspace(*text)) text++;
	return text;
}

str GetWord(StringSpan* ss, str text)
{
	text = SwallowWhitespace(text);
	ss->str = text;
	while (*text && isalnum(*text)) text++;
	ss->len = text - ss->str;
	return text;
}

///************************************************************************/
/// MatchItem
///************************************************************************/


void DestroyItem(MatchItem* item)
{
	MatchItem* it = item->FirstChild;
	while (it)
	{
		MatchItem* next = it->NextSibling;
		DestroyItem(it);
		it = next;
	}
	free(item);
}

void AppendChild(MatchItem* item, MatchItem* child)
{
	if (!item->FirstChild)
	{
		child->NextSibling = child->PrevSibling = 0;

		item->FirstChild = child;
		item->LastChild = child;
	} else
	{
		child->NextSibling = 0;
		child->PrevSibling = item->LastChild;

		item->LastChild->NextSibling = child;
		item->LastChild = child;
	}
}

///************************************************************************/
/// Matchers
///************************************************************************/

static MatchItem global_links;
static MatchItem mi_float, mi_word, mi_int, mi_ignore;

#define OUT(type, val) { type* _out = va_arg(opts->OutList, type*); *_out = (type)val; }

str MatchIgnore(MatchItem* item, MatchOptions* opts, str text)
{
	return text;
}

void OnScanIgnore(MatchItem* it, StringSpan start, StringSpan end, MatchOptions* opts)
{
	va_arg(opts->OutList, void*);
}

str MatchWord(MatchItem* item, MatchOptions* opts, str text)
{
	if (!isalpha(*text++)) return 0;
	while (*text && isalnum(*text)) text++;
	return text;
}

str MatchFloat(MatchItem* item, MatchOptions* opts, str text)
{
	str end = 0;
	strtod(text, (char**)&end);
	if (text == end)
		return 0;
	return end;
}

void OnScanFloat(MatchItem* it, StringSpan start, StringSpan end, MatchOptions* opts)
{
	OUT(float, strtod(start.str, 0));
}

str MatchLiteral(MatchItem* item, MatchOptions* opts, str text)
{
	int res = SSCmpStr(&item->Literal, text);
	if (!res) return 0;
	return text + item->Literal.len;
}

str MatchLink(MatchItem* item, MatchOptions* opts, str text)
{
	if (item->Link)
		return item->Link->MatchFun(item, opts, text);
	return 0;
}

str MatchGroup(MatchItem* item, MatchOptions* opts, str text)
{
	size_t match_count = 0;

	while (true)
	{
		int total_match = true;
		str start_match = text;
		MatchItem* it = 0;

		for (it = item->FirstChild; it; it = it->NextSibling)
		{
			str start_match_item = text;
			int match = false;

			if (opts && opts->IgnoreWhitespace)
				start_match_item = text = SwallowWhitespace(text);

			text = it->MatchFun(it, opts, text);
			if (text == 0)
			{
				if (it->Negate)
				{
					match = true;
					text = start_match_item+1;
				} else
				{
					total_match = false;
					break;
				}
			} 
			else if (text)
			{
				if (it->Negate)
				{
					total_match = false;
					break;
				} else 
				{
					match = true;
				}
			}

			if (match)
			{
				if (it->Output)
				{
					ParseFunction pfun = it->OnScanMatch;
					if (!pfun && it->Type == MATCH_LINK && it->Link)
						pfun = it->Link->OnScanMatch;
						
					if (pfun && opts->OutList)
						pfun(it, SSMake(start_match_item, text), it->OutName, opts);
				}
			}
		}

		if (total_match)
		{
			match_count++;
			if (item->Multiple == '?' && match_count == 1)
				break;
			else if (item->Multiple == 0)
				break;
		} 
		else
		{
			text = start_match; /// rewind to last successful match

			if (item->Multiple == '+' && match_count == 0) /// at least once
				return 0;
			else if (item->Multiple == '?' || item->Multiple == '*') /// zero or once, or zero or more
				break;
			else /// exactly once
				return 0;
		}
	}

	return text;
}

///************************************************************************/
/// Links
///************************************************************************/

static str ParseError = 0;
static str ParsePos = 0;

void StartREScan()
{
	ParseError = 0;
	ParsePos = 0;

	SSSet(&mi_word.Name, "word");
	mi_word.Type = MATCH_BUILTIN;
	mi_word.MatchFun = MatchWord;
	AppendChild(&global_links, &mi_word);

	SSSet(&mi_float.Name, "float");
	mi_float.Type = MATCH_BUILTIN;
	mi_float.MatchFun = MatchFloat;
	mi_float.OnScanMatch = OnScanFloat;
	AppendChild(&global_links, &mi_float);

	SSSet(&mi_ignore.Name, "ignore");
	mi_ignore.Type = MATCH_BUILTIN;
	mi_ignore.MatchFun = MatchIgnore;
	mi_ignore.OnScanMatch = OnScanIgnore;
	AppendChild(&global_links, &mi_ignore);
}

void AddLink(StringSpan name, MatchItem* item)
{
	item->Name = name;
	AppendChild(&global_links, item);
}

MatchItem* GetLink(StringSpan* name)
{
	MatchItem* it = 0;
	for (it = global_links.FirstChild; it; it = it->NextSibling)
	{
		if (SSCmp(&it->Name, name))
			return it;
	}
	return 0;
}

///************************************************************************/
/// Parsing
///************************************************************************/

#define make(type) ((type*)calloc(1,sizeof(type)))

str ParseLiteral(MatchItem** it, str text)
{
	MatchItem* item = *it = make(MatchItem);
	char terminator = *text;

	item->Type = MATCH_LITERAL;
	item->MatchFun = MatchLiteral;

	text++; /// '

	SSStart(&item->Literal, text);
	while (*text && (*text != terminator || *(text-1) == '\\'))
		text++;
	SSStop(&item->Literal, text);

	if (*text)
		text++; /// '

	return text;
}

str ParseLink(MatchItem** it, str text)
{
	StringSpan link;
	MatchItem* item = *it = make(MatchItem);
	item->Type = MATCH_LINK;
	item->MatchFun = MatchLink;

	text = GetWord(&link, text);

	item->Link = GetLink(&link);

	return text;
}

/// TODO:
/// - . - matches any character
/// - | - pipe operator in groups; maybe add NextSiblingIsAlternative to MatchItem?
/// - matching literals with escaped characters in them
/// - {n[,m]} for min/max repetitions
/// - output options (like width for ints/floats)
/// - ability to specify max-chars-to-read for an item (like a word)

str ParseGroup(MatchItem** it, str text)
{
	MatchItem* item = *it = make(MatchItem);
	item->Type = MATCH_GROUP;
	item->MatchFun = MatchGroup;

	while (*text && *text != ')')
	{
		int negate = false;
		MatchItem* child = 0;

		text = SwallowWhitespace(text);

		if (*text == '^')
		{
			negate = true;
			text++;
		}

		if (*text == '\'' || *text == '"')
		{
			text = ParseLiteral(&child, text);
		} 
		else if (*text == '(')
		{
			text = ParseGroup(&child, text+1);
			if (*text == ')')
				text++;
		} 
		else if (isalpha(*text))
		{
			text = ParseLink(&child, text);
		} else
		{
			DestroyItem(item);
			*it = 0;
			ParseError = "Unrecognized character";
			ParsePos = text;
			return 0;
		}

		if (child)
		{
			child->Negate = negate;

			if (*text == '?' || *text == '*' || *text == '+')
			{
				child->Multiple = *text++;
			}

			if (*text == ':')
			{
				child->Output = true;
				text = GetWord(&child->OutName, ++text);
			}
			AppendChild(item, child);
		} else
		{
			DestroyItem(item);
			*it = 0;
			return 0;
		}

		text = SwallowWhitespace(text);
	}

	return text;
}

str Scan(str in, str format, ...)
{
	void** out = 0;
	MatchOptions opts;
	MatchItem* item = 0;
	str ret = 0;

	va_list ap;
	va_start(ap, format);

	opts.IgnoreWhitespace = false;
	opts.OutList = ap;

	ParseGroup(&item, format);

	ret = MatchGroup(item, &opts, in);

	va_end(ap);

	return ret;
}